/*
 * Copyright (c) 2013, Tomas Zima
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1) Redistributions of source code must retain the above copyright notice, this list
 *    of conditions and the following disclaimer.
 * 2) Redistributions in binary form must reproduce the above copyright notice, this list 
 *    of conditions and the following disclaimer in the documentation and/or other materials 
 *    provided with the distribution.
 * 3) Neither the name of the Tomas Zima nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package jgamio.framework.application;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import jgamio.framework.application.KeyboardHandler.KeyEvent;
import jgamio.framework.application.KeyboardHandler.KeyPressType;
import jgamio.framework.application.exceptions.EnumDispatcherNotDefined;
import jgamio.framework.application.exceptions.InvalidConfigurationFile;
import jgamio.framework.application.injection.SetComponentsList;
import jgamio.framework.application.injection.SetEnumDispatcher;
import jgamio.framework.application.injection.SetKeyboardHandler;
import jgamio.framework.examples.example01.GameCommands;

import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Class for loading of XML files, which contains definition of keyboard map.
 * It will automatically pass loaded values into the keyboard handler, so you
 * do not need to do it yourself. 
 * 
 * @author Tomas Zima
 */
// Warnings about unused properties are currently suppressed. These values
// will be needed later.
@SuppressWarnings("unused")
public class KeyboardConfigLoader implements SetComponentsList, SetEnumDispatcher, SetKeyboardHandler
{
	/** Reference to the KeyboardHandler (for registering events). */
	private KeyboardHandler	keyboardHandler;

	/** Reference to the EnumDispatcher (for translating of values). */
	private EnumDispatcher enumDispatcher;
	
	/** Reference to the ComponentsList (for translating of components identifier). */
	private ComponentsList	componentsList;
	
	/**
	 * <p>
	 * Constructor.
	 * </p>
	 * 
	 * <p>
	 * Saves references to following objects:
	 * 	<ul>
	 * 		<li>KeyboardHandler - All loaded informations will be passed to this.</li>
	 * 		<li>EnumDispatcher - Values will be translated according to informations given by this object.</li>
	 * 		<li>ComponentsList - Identifier of components will be translated to their instances according to this object.</li>
	 * 	</ul>
	 * </p>
	 * 
	 * @param filename
	 * @param keyboardHandler
	 * @param componentsList
	 */
	public KeyboardConfigLoader(KeyboardHandler keyboardHandler, EnumDispatcher enumDispatcher, ComponentsList componentsList)
	{
		// Save given informations
		this.keyboardHandler	= keyboardHandler;
		this.enumDispatcher		= enumDispatcher;
		this.componentsList		= componentsList;
	}

	/**
	 * <p>
	 * Constructor.
	 * </p>
	 * 
	 * <p>
	 * Saves references to following objects and immediately loads specified file.
	 * 	<ul>
	 * 		<li>KeyboardHandler - All loaded informations will be passed to this.</li>
	 * 		<li>EnumDispatcher - Values will be translated according to informations given by this object.</li>
	 * 		<li>ComponentsList - Identifier of components will be translated to their instances according to this object.</li>
	 * 	</ul>
	 * </p>
	 * 
	 * @param filename
	 * @param keyboardHandler
	 * @param enumDispatcher
	 * @param componentsList
	 * @throws EnumDispatcherNotDefined
	 * @throws InvalidConfigurationFile
	 */
	public KeyboardConfigLoader(String filename, KeyboardHandler keyboardHandler, EnumDispatcher enumDispatcher, ComponentsList componentsList) throws EnumDispatcherNotDefined, InvalidConfigurationFile
	{
		this(keyboardHandler, enumDispatcher, componentsList);
		this.loadConfigFile(filename);
	}

	/**
	 * <p>
	 * It will load defined XML file and pass these informations 
	 * into the keyboard handler.
	 * </p>
	 * 
	 * <p>
	 * Structure of the XML document is pretty simple. Whole content
	 * is under the keyboardMap tag. It contains nodes called component -
	 * for example for menu, game etc. In these components, there are
	 * only tags action with parameters key, pressType and value.
	 * </p>
	 */
	public void loadConfigFile(String filename) throws EnumDispatcherNotDefined, InvalidConfigurationFile
	{
		/*
		 * Load XML file.
		 */
		try
		{
			File				xmlFile			= new File(filename);
			DocumentBuilder		documentBuilder	= DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document			document		= documentBuilder.parse(xmlFile);

			NodeList			components		= document.getElementsByTagName("component");

			// Iterate through the components
			for (int i = 0; i < components.getLength(); i++)
			{
				int		componentId		= Integer.valueOf(components.item(i).getAttributes().getNamedItem("id").getNodeValue());
				String	componentTitle	= components.item(i).getAttributes().getNamedItem("title").getNodeValue();

				if (!enumDispatcher.hasEnumDispatcher(componentsList.getComponentByIdentifier(componentId)))
				{
					Sys.alert("Undefined enum dispatcher", "Enum dispatcher for this component (" + componentId + ") was not defined!");
					throw new EnumDispatcherNotDefined("Enum dispatcher for this component (" + componentId + ") was not defined!");
				}
				
				// Iterate trough the actions
				for (int j = 0; j < components.item(i).getChildNodes().getLength(); j++)
				{
					// Save action
					Node action = components.item(i).getChildNodes().item(j);
	
					// Is it really element? Not a content, for example?
					if (action.getNodeType() == Node.ELEMENT_NODE)
					{
						// Read attributes
						String key			= action.getAttributes().getNamedItem("key").getNodeValue();
						String pressType	= action.getAttributes().getNamedItem("pressType").getNodeValue();
						String value		= action.getAttributes().getNamedItem("value").getNodeValue();

						this.keyboardHandler.addEvent(
							componentsList.getComponentByIdentifier(componentId),
							Keyboard.getKeyIndex(key.toUpperCase()),
							keyboardHandler.new KeyEvent(this.enumDispatcher.getEnumDispatcher(componentsList.getComponentByIdentifier(componentId)).getValueOf(value)),
							KeyPressType.valueOf(KeyPressType.class, pressType.toUpperCase())
						);
					}
				}
			}
		}
		catch (ParserConfigurationException e)
		{
			Sys.alert("Keyboard configuration file cannot be load!", "Requested file with keyboard map cannot be load.!");
			throw new InvalidConfigurationFile("Requested file with keyboard map cannot be load.!");
		}
		catch (SAXException e)
		{
			Sys.alert("Keyboard configuration file is invalid!", "Keyboard map, which was attempted to load (" + filename + "), is not valid.");
			throw new InvalidConfigurationFile("Keyboard map, which was attempted to load (" + filename + "), is not valid.");
		}
		catch (IOException e)
		{
			Sys.alert("Keyboard configuration file was not found!", "Requested file with keyboard map (" + filename + ") was not found.");
			throw new InvalidConfigurationFile("Requested file with keyboard map (" + filename + ") was not found.");
		}
		catch (NullPointerException e)
		{
			e.printStackTrace();
			Sys.alert("Keyboard configuration file is invalid!", "Keyboard map is probably incomplete and you miss some elements. Are you confident with XML structure?");
			throw new InvalidConfigurationFile("Keyboard map is probably incomplete and you miss some elements. Are you confident with XML structure?");
		}
		catch (IllegalArgumentException e)
		{
			Sys.alert("Keyboard configuration file is invalid!", "Some constant value (such as those in attributes pressType) is not valid.");
			throw new InvalidConfigurationFile("Some constant value (such as those in attributes pressType) is not valid.");
		}
	}

	/**
	 * Sets reference to the {@link KeyboardHandler}.
	 */
	@Override
	public void setKeyboardHandler(KeyboardHandler keyboardHandler)
	{
		this.keyboardHandler = keyboardHandler;
	}

	/**
	 * Sets reference to the {@link EnumDispatcher}.
	 */
	@Override
	public void setEnumDispatcher(EnumDispatcher enumDispatcher)
	{
		this.enumDispatcher = enumDispatcher;
	}

	/**
	 * Sets reference to the {@link ComponentsList}. 
	 */
	@Override
	public void setComponentsList(ComponentsList componentsList)
	{
		this.componentsList = componentsList;
	}
}
